#!/usr/bin/env python3
import sys
from PyQt5.QtCore import *
#from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( QAction, QApplication,  QMessageBox, QTextEdit, QMenu, QFileDialog)
from PyQt5.QtGui import  QCursor, QFont, QColor, QPixmap, QTextCursor, QPalette
from PyQt5.QtPrintSupport import *

from PyQt5.Qsci import QsciLexerPython, QsciScintilla, QsciAPIs,QsciScintillaBase 
#from PyQt5.Qsci import QsciDocument
from qrc_resources  import   *
from lexer_c_getfunc import g_allFuncList
import keyword
class MyLexerCPP(QsciLexerPython):
    def __init__(self,parent):
        QsciLexerPython.__init__(self,parent)
        if parent == None:
            return
        self.setFont(self.parent().Font)
        #颜色由主界面传入，存于《setting.ini》中
        # self.setColor(QColor(self.parent().win.colorDict[ 'Default'  ]))    #设置前景色
        # self.setPaper(QColor(self.parent().win.colorDict[ 'Background'  ]))    #设置底色
        
        # self.setColor(QColor(self.parent().win.colorDict['Default'   ]), QsciLexerCPP.Default       ) 
        # self.setColor(QColor(self.parent().win.colorDict['Keyword'                    ]), QsciLexerCPP.Keyword       )  
        # self.setColor(QColor(self.parent().win.colorDict['KeywordSet2'              ]), QsciLexerCPP.KeywordSet2       )           
        # self.setColor(QColor(self.parent().win.colorDict['CommentDoc'               ]), QsciLexerCPP.CommentDoc    )#文档注释 /**开头的颜色
        # self.setColor(QColor(self.parent().win.colorDict['Comment'                    ]), QsciLexerCPP.Comment       )#块注释 的颜色
        # self.setColor(QColor(self.parent().win.colorDict['CommentLine'              ]), QsciLexerCPP.CommentLine   )#行注释的颜色
        # self.setColor(QColor(self.parent().win.colorDict['Number'                      ]), QsciLexerCPP.Number            )       #数字 的颜色
        # self.setColor(QColor(self.parent().win.colorDict['DoubleQuotedString'   ]), QsciLexerCPP.DoubleQuotedString)#双引号字符串的颜色
        # self.setColor(QColor(self.parent().win.colorDict['SingleQuotedString'   ]), QsciLexerCPP.SingleQuotedString)#单引号字符的颜色
        # self.setColor(QColor(self.parent().win.colorDict['PreProcessor'            ]), QsciLexerCPP.PreProcessor      )#预编译语句的颜色
        # self.setColor(QColor(self.parent().win.colorDict['Operator'                   ]), QsciLexerCPP.Operator          )
        # self.setColor(QColor(self.parent().win.colorDict['UnclosedString'         ]), QsciLexerCPP.UnclosedString)#未完成输入的字符串的颜色
        #self.setColor(QColor("#000000"), QsciLexerCPP.Identifier)  #可识别字符的颜色，这个范围很广，包含了关键词，函数名；所以要取消这句
        
        font = QFont(self.parent().Font)
        font.setBold(True)
        self.setFont(font,5)    #默认的字体加粗。
        
        font = QFont(self.parent().Font)
        font.setItalic(True)        
        # self.setFont(font,QsciLexerCPP.Comment)   #注释的字体用斜体。
        
        for i in range(0, QsciScintillaBase.STYLE_MAX):
            desc = self.description(i)
            if desc: 
                self.setEolFill(True, i)    #将填充的底色延伸到屏幕的右端；否则默认只有文字的地方。

class SciTextEdit(QsciScintilla):
    NextId = 1

    def __init__(self, filename='', wins=None, parent=None):
        global g_allFuncList
        super(QsciScintilla, self).__init__(parent)
        self.win=wins
        self.jumpName=''
        self.list_line=[]
        self.Font = self.win.EditFont   #采用主窗口传入的字体
        self.Font.setFixedPitch(True)
        self.setFont(self.Font)
        
        #1.设置文档的编码格式为 “utf8” ，换行符为 windows   【可选linux，Mac】
        self.setUtf8(True)        
        self.setEolMode(QsciScintilla.SC_EOL_CRLF)#文件中的每一行都以EOL字符结尾（换行符为 \r \n）
        #2.设置括号匹配模式
        self.setBraceMatching(QsciScintilla.StrictBraceMatch)# 
        #3.设置 Tab 键功能
        self.setIndentationsUseTabs(True)#行首缩进采用Tab键，反向缩进是Shift +Tab
        self.setIndentationWidth(4)     #行首缩进宽度为4个空格
        self.setIndentationGuides(True)#    显示虚线垂直线的方式来指示缩进
        self.setTabIndents(True)    #编辑器将行首第一个非空格字符推送到下一个缩进级别
        self.setAutoIndent(True)    #插入新行时，自动缩进将光标推送到与前一个相同的缩进级别
        self.setBackspaceUnindents(True)
        self.setTabWidth(4)         # Tab 等于 4 个空格
        #4.设置光标
        self.setCaretWidth(2)           #光标宽度（以像素为单位），0表示不显示光标
        self.setCaretForegroundColor(QColor("darkCyan"))    #光标颜色
        self.setCaretLineVisible(True)      #是否高亮显示光标所在行
        self.setCaretLineBackgroundColor(QColor(self.win.colorDict[ 'CaretLine'  ]))     #光标所在行的底色
        #5.设置页边特性。        这里有3种Margin：[0]行号    [1]改动标识   [2]代码折叠
            #5.1 设置行号
        self.setMarginsFont(self.Font)      #行号字体
        self.setMarginLineNumbers(0,True)    #设置标号为0的页边显示行号    
        self.setMarginWidth(0,'00000')  #行号宽度
        self.setMarkerForegroundColor(QColor("#FFFFFF"),0)  
        self.setMarginsBackgroundColor(QColor(self.win.colorDict[ 'Background'  ]))
        #self.setMarkerBackgroundColor(QColor(self.win.colorDict[ 'Background'  ]),0)
        #self.setMarkerBackgroundColor(QColor(self.win.colorDict[ 'Background'  ]),1)
        #self.setMarkerBackgroundColor(QColor(self.win.colorDict[ 'Background'  ]),2)  
        
            #5.2 设置改动标记    
        self.setMarginType(1, QsciScintilla.SymbolMargin)   # 设置标号为1的页边用于显示改动标记 
        self.setMarginWidth(1, "0000")          #改动标记占用的宽度
        #img = QPixmap(":/leftside.png")     #改动标记图标，大小是48 x 48
        #sym_1 = img.scaled(QSize(16, 16))       #图标缩小为 16 x 16 #         
        sym_1 = QsciScintilla.LeftRectangle
        
        self.markerDefine(sym_1, 0)     
        self.setMarginMarkerMask(1, 0b1111)
        self.setMarkerForegroundColor(QColor("#ff0000"),0)  
        self.setMarkerBackgroundColor(QColor("#00ff00"),0)  #00ff00    
            #5.3  设置代码自动折叠区域
        self.setFolding(QsciScintilla.PlainFoldStyle)
        self.setMarginWidth(2,12)
                #5.3.1 设置代码折叠和展开时的页边标记 - +
        self.markerDefine(QsciScintilla.Minus, QsciScintilla.SC_MARKNUM_FOLDEROPEN)
        self.markerDefine(QsciScintilla.Plus, QsciScintilla.SC_MARKNUM_FOLDER)
        self.markerDefine(QsciScintilla.Minus, QsciScintilla.SC_MARKNUM_FOLDEROPENMID)
        self.markerDefine(QsciScintilla.Plus, QsciScintilla.SC_MARKNUM_FOLDEREND)
                #5.3.2 设置代码折叠后，+ 的颜色FFFFFF
        self.setMarkerBackgroundColor(QColor("#FFBCBC"), QsciScintilla.SC_MARKNUM_FOLDEREND)
        self.setMarkerForegroundColor(QColor("red"), QsciScintilla.SC_MARKNUM_FOLDEREND)
    
        #6.语法高亮显示
            #6.1语法高亮的设置见 MyLexerCPP类 源码
        self.lexer=MyLexerCPP(self)
        self.setLexer(self.lexer)
            #6.2设置自动补全
        self.mod=False
        self.__api = QsciAPIs(self.lexer)
            # SDCC编译器的关键字 列表
        # sdcc_kwlist=['__data','__idata','__pdata','__xdata','__code','__bit','__sbit',
        #                 '__sfr' , 'u8', 'u16' , 'WORD', 'BYTE','define' , 'include','__interrupt',  
        #                 '__critical', '__using','double' , 'int' , 'struct' , 'break' , 'else' ,  
        #                 'auto' ,'switch' , 'case','enum' , 'register' , 'typedef' , 'default' , 
        #                 'char' , 'extern' , 'return' , 'union' , 'const' , 'float','long' ,
        #                'short' , 'unsigned' , 'continue' , 'for' , 'signed' , 'void' , 
        #                'goto','sizeof' , 'volatile' , 'do' , 'while' , 'static' , 'if', 
        #                'endif', 'ifndef', 'ifdef']
        autocompletions = keyword.kwlist#+sdcc_kwlist
        for ac in autocompletions:
            self.__api.add(ac)
        self.__api.prepare()
        self.autoCompleteFromAll()
        self.setAutoCompletionSource(QsciScintilla.AcsAll) #自动补全所以地方出现的
        self.setAutoCompletionCaseSensitivity(True) #设置自动补全大小写敏感
        self.setAutoCompletionThreshold(1);     #输入1个字符，就出现自动补全 提示
        self.setAutoCompletionReplaceWord(False)
        self.setAutoCompletionUseSingle(QsciScintilla.AcusExplicit)
        self.setAttribute(Qt.WA_DeleteOnClose)
        #设置函数名为关键字2    KeyWord = sdcc_kwlistcc  ;KeywordSet2 = 函数名
        # self.SendScintilla(QsciScintilla.SCI_SETKEYWORDS, 0," ".join(sdcc_kwlist).encode(encoding='utf-8'))
        #self.SendScintilla(QsciScintilla.SCI_STYLESETFORE, QsciLexerCPP.KeywordSet2, 0x7f0000)
        self.SendScintilla(QsciScintilla.SCI_SETKEYWORDS, 1," ".join(g_allFuncList).encode(encoding='utf-8'))
        
        
        self.filename = filename
        if self.filename=='':
            self.filename = str("未命名-{0}".format(SciTextEdit.NextId))
            SciTextEdit.NextId += 1
        self.setModified(False)
        #设置文档窗口的标题
        self.setWindowTitle(QFileInfo(self.filename).fileName())
        #将槽函数链接到文本改动的信号
        self.textChanged.connect(self.textChangedAction)
        #给文档窗口添加右键菜单
        self.setContextMenuPolicy(Qt.CustomContextMenu)#
        self.customContextMenuRequested.connect(self.RightMenu)
       
    def getIncludeFile(self, linestr):
        start=0
        end=0
        for i in range(7, len(linestr)):
            if start !=0 and linestr[i] in ' >"':
                end = i+1
                return linestr[start: end]
            elif start ==0 and linestr[i] in '<"':
                start = i
        return None 
    
    def getFuncNameInLine(self, linestr):     #在行中查找函数名  
        start=0
        end=linestr.find('(')
        if end == 0:
            return None
        for i in range(end-1, -1, -1):  #从 左括号字符 “(” 往回查询到空格、= 、制表符 就停止
            if start !=0 and  linestr[i] in " =\t":
                end = start
                start = i+1
                break
            # 左括号 和 函数名 之间有可能包含空格
            elif start ==0 and linestr[i] != ' ' :  #非空格，就是函数名最后一个字符，把位置保存在start
                start = i+1
        else:
            end=0
        return linestr[start: end]

    def RightMenu(self):
        line_num, index=self.getCursorPosition()
        text=self.text()
        #0.获取光标所在行的全部字符
        str1=text.splitlines(False)[line_num]
        if len(str1) != 0:
            #0.1 如果字符串有 ‘#in’ ，那么应该包含文件名
            if '#in' in str1:     
                self.jumpName = self.getIncludeFile(str1)
            #0.2 如果含有 字符 (  ,那么应该有函数名
            elif '(' in str1:  
                self.jumpName= self.getFuncNameInLine(str1)
        #1.Jump to
        self.popMenu = QMenu()
        Jump2Function=QAction('Jump to '+self.jumpName, self)
        self.popMenu.addAction(Jump2Function)
        Jump2Function.triggered.connect(self.do_Jump2Function)
        #setEnabled   isRedoAvailable  isUndoAvailable
        #2.undo    
        undoAction =QAction('Undo', self)
        undoAction.triggered.connect(self.undo)
        undoAction.setEnabled(self.isUndoAvailable())
        self.popMenu.addAction(undoAction)
        #3.redo 
        redoAction =QAction('Redo', self)
        redoAction.triggered.connect(self.redo)
        redoAction.setEnabled(self.isRedoAvailable())
        self.popMenu.addAction(redoAction)
        #4.copy      
        copyAction =QAction('Copy', self)
        copyAction.triggered.connect(self.copy)
        self.popMenu.addAction(copyAction)
        #5.cut 
        cutAction =QAction('Cut', self)
        cutAction.triggered.connect(self.cut)
        self.popMenu.addAction(cutAction)
        #6.paste
        pasteAction =QAction('Paste', self)
        pasteAction.triggered.connect(self.paste)
        self.popMenu.addAction(pasteAction)
         #7.在鼠标位置显示右菜单    
        self.popMenu.exec_(QCursor.pos())
    def do_Jump2Function(self): # 跳转到函数 ；跳转到包含文件
        if self.jumpName[0] in '"<' :
            self.win.Jump2IncludeFile_Signal.emit(self.jumpName)
        else:
            self.win.Jump2Func_Signal.emit(self.jumpName)
    
    def closeEvent(self, event):
        if (self.isModified() and 
            QMessageBox.question(self,
                   "文本编辑器 - 未保存的更改",
                   "保存在 {0} 文件中所做出的更改?".format(self.filename),
                   QMessageBox.Yes|QMessageBox.No) ==
                QMessageBox.Yes):
            try:
                self.save()
            except EnvironmentError as e:
                QMessageBox.warning(self,
                        "文本编辑器 - 保存错误",
                        "保存失败 {0}: {1}".format(self.filename, e))

    def textChangedAction(self):
        line, index=self.getCursorPosition()    #获取当前光标所在行
        handle_01 = self.markerAdd(line, 0)     # 添加改动标记
        self.list_line.append(handle_01)        #保存改动标记所在的地方，给后面保存文档时消除标记提供信息
        
    def save(self):
        if self.filename[:3]=='未命名':
            if self.filename[-4:]==".txt":
                self.filename=self.filename[:-4]
            filename = QFileDialog.getSaveFileName(self, "文本编辑器 - 另存为", self.filename,
                    "c files (*.c *.*)")
            if filename[0]=='':
                return
            self.filename = filename[0]
        self.setWindowTitle(QFileInfo(self.filename).fileName())
        exception = None
        fh = None
        try:
            fh = QFile(self.filename)
            if not fh.open(QIODevice.WriteOnly):
                raise IOError(str(fh.errorString()))
            stream = QTextStream(fh)
            stream.setCodec("UTF-8")
            stream << self.text()
            #self.document().setModified(False)
            self.setModified(False)
        except EnvironmentError as e:
            exception = e
        finally:
            if fh is not None:
                fh.close()
            if exception is not None:
                raise exception
        #将保存过的文件消除改动标识,清空记录改动的列表
        for line in self.list_line:
            self.markerDeleteHandle(line)
        self.list_line.clear()

    def load(self):
        exception = None
        fh = None
        try:
            fh = QFile(self.filename)
            if not fh.open(QIODevice.ReadOnly):
                raise IOError(str(fh.errorString()))
            stream = QTextStream(fh)
            stream.setCodec("UTF-8")
            self.setText(stream.readAll())
            #self.document().setModified(False)
            self.setModified(False)
        except EnvironmentError as e:
            exception = e
        finally:
            if fh is not None:
                fh.close()
            if exception is not None:
                raise exception
'''
if __name__=="__main__":
    app=QApplication(sys.argv)
    form=TextEdit()
    form.show()
    app.exec_()
        
    def mousePressEvent(self, event):
        """Overloaded mouse click event"""
        #Execute the superclass mouse click event
        super().mousePressEvent(event)
        #Set focus to the clicked editor
        self.setFocus()
        # Hide the function wheel if it is shown
        key1=event.button()
        #模拟鼠标左键单击 QMouseEvent(QEvent::Type type, const QPointF &localPos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers)
        #event.button()=QtCore.Qt.LeftButton
        
        if key1 == QtCore.Qt.RightButton:
            print("right mouse")
     
    def keyPressEvent(self, QKeyEvent):#重写按键事件
        super().keyPressEvent(QKeyEvent)
        key = QKeyEvent.key()
        if key == Qt.Key_ParenLeft:
            #print("( press down")
            pos = self.SendScintilla(QsciScintilla.SCI_GETCURRENTPOS) #取得当前位置，注释掉的代码在包含中文代码中出错。
            start = self.SendScintilla(QsciScintilla.SCI_WORDSTARTPOSITION,  pos-2) # 纯英文下，运行正常
            end = self.SendScintilla(QsciScintilla.SCI_WORDENDPOSITION,  pos-2)
            self.SendScintilla(QsciScintilla.SCI_CALLTIPSHOW, pos, self.text()[start:end] )
            #self.CallTipShow(pos, self.text()[start:end] )
        elif key == Qt.Key_ParenRight:
            self.SendScintilla(QsciScintilla.SCI_CALLTIPCANCEL)
'''

class QtextEditClick(QTextEdit):
    TextEdit2Click_Signal = pyqtSignal()
    
    def __init__(self, parent=None):
        super(QtextEditClick, self).__init__(parent)        
        self.textChanged.connect(self.textChangedAction)#将槽函数链接到文本改动的信号
        
    def mouseDoubleClickEvent(self, e):
        self.TextEdit2Click_Signal.emit()   #产生一个文本双击信号
        
    def textChangedAction(self):
        cursor=self.textCursor()
        cursor.movePosition(QTextCursor.End)#移动鼠标至文档最后
        self.setTextCursor(cursor)
        QApplication.processEvents()
